package controller

import (
	"fmt"
	"gitee.com/kristas/booting-go/framework/common/util/lang"
	"gitee.com/kristas/booting-go/framework/common/util/list"
	"gitee.com/kristas/booting-go/framework/common/util/reflectx"
	"gitee.com/kristas/booting-go/framework/web"
	"reflect"
)

func RestApiExtract(controller web.Controller, handler func(apiInfo web.ApiInfo, svm web.ServiceMethod)) {
	typ := reflect.TypeOf(controller)
	val := reflect.ValueOf(controller)
	group := controller.Group()
	reflectx.ForEachField(controller, false, func(field reflect.StructField, value reflect.Value) error {
		if !isRestAPI(field) {
			return nil
		}
		if method, ok := typ.MethodByName(methodName(field)); ok {
			methodVal := val.MethodByName(methodName(field))
			svm := NewServiceMethod(&method, &methodVal)
			info := NewApiInfo(group, field, svm)
			handler(info, svm)
		}
		return nil
	})
}

func FsApiExtract(controller web.FsController, handler func(apiInfo web.ApiInfo, svm web.ServiceMethod)) {
	typ := reflect.TypeOf(controller)
	val := reflect.ValueOf(controller)
	//compile := controller.Compile()
	prefix := controller.Group()
	reflectx.ForEachField(controller, false, func(field reflect.StructField, value reflect.Value) error {
		if isFsAPI(field) {
			return nil
		}
		if method, ok := typ.MethodByName(methodName(field)); ok {
			methodVal := val.MethodByName(methodName(field))
			svm := NewServiceMethod(&method, &methodVal)
			info := NewApiInfo(prefix, field, svm)
			handler(info, svm)
		}
		return nil
	})
}

func HandleFsApi(controller web.Controller, handler func(prefix, dir string)) {
	typ := reflect.TypeOf(controller)
	val := reflect.ValueOf(controller)
	group := controller.Group()
	reflectx.ForEachField(controller, false, func(field reflect.StructField, value reflect.Value) error {
		if isFsAPI(field) {
			if _, ok := typ.MethodByName(methodName(field)); ok {
				url := field.Tag.Get("url")
				url = group + url
				methodVal := val.MethodByName(methodName(field))
				handler(url, methodVal.Call(nil)[0].String())
			}
		}
		return nil
	})
}

func isRestAPI(field reflect.StructField) bool {
	return field.Type.Implements(reflect.TypeOf(new(web.RestMethod)).Elem())
}

func isFsAPI(field reflect.StructField) bool {
	return field.Type == reflect.TypeOf(web.FS{})
}

func findHttpMethodAndUrl(field reflect.StructField) (method string, url string) {
	m := reflect.New(field.Type).Interface().(web.RestMethod)
	method = m.GetType()
	url = field.Tag.Get("url")
	return
}

func methodName(field reflect.StructField) string {
	method := lang.NewString(field.Name).FirstLetterUpper()
	return method.String()
}

func buildParams(param string, method reflect.Type) []web.Param {
	var paramQueue = make([]web.Param, 0)
	paramStr := formatParamStr(param)
	if paramStr.IsEmpty() {
		return paramQueue
	}
	params := paramStr.ReplaceAll(" ", ",").Split(",")
	var scope = web.DefaultSource
	for i := params.Length() - 1; i >= 0; i-- {
		e := params[i]
		if list.NewList(web.SourceDefine).Contains(e.String()) {
			scope = e.String()
		} else {
			var p *Param
			arr := e.SplitN(":", 2)
			switch arr.Length() {
			case 1:
				p = &Param{
					name:   arr[0].String(),
					source: scope,
				}
			case 2:
				p = &Param{
					name:        arr[0].String(),
					source:      scope,
					specifyType: arr[1].String(),
				}
			default:
				panic(fmt.Sprintf("param: %s invalid", e))
			}
			paramQueue = append(paramQueue, p)
		}
	}
	reverseParam(paramQueue)
	if method != nil {
		for i := 0; i < method.NumIn()-1; i++ {
			in := method.In(i + 1)
			paramQueue[i].SetType(in)
		}
	}
	return paramQueue
}

func formatParamStr(param string) *lang.String {
	paramStr := lang.NewString(param).RemovePrefix("[").RemoveSuffix("]")
	ls, rs := paramStr.Contains(" ,"), paramStr.Contains(", ")
	for ls || rs {
		if ls {
			paramStr = paramStr.ReplaceAll(" ,", ",")
		}
		if rs {
			paramStr = paramStr.ReplaceAll(", ", ",")
		}
		ls, rs = paramStr.Contains(" ,"), paramStr.Contains(", ")
	}
	return paramStr
}

func reverseParam(param []web.Param) {
	for i := range param {
		if i == len(param)/2 {
			break
		}
		param[i], param[len(param)-1-i] = param[len(param)-1-i], param[i]
	}
}

func getDescription(field reflect.StructField) string {
	desc := field.Tag.Get("desc")
	if desc == "" {
		desc = lang.NewString(field.Name).SnakeCase().ReplaceAll("_", " ").String()
	}
	return desc
}
